using System.Drawing;
using System.Xml;
using Wilco.SyntaxHighlighting.Engine.Node;
using Wilco.SyntaxHighlighting.Engine.Node.Implementation.CommentBlock;
using Wilco.SyntaxHighlighting.Engine.Node.Occurrence;
using Wilco.SyntaxHighlighting.Engine.Scanner;
using Wilco.SyntaxHighlighting.Engine.Tokenizer;

namespace Wilco.SyntaxHighlighting.Engine.Scanner.Implementation
{
    /// <summary>
    /// Represents a comment block scanner.
    /// </summary>
    public class CommentBlockScanner : ScannerBase
    {
        private CommentBlockNode commentBlockNode;

        /// <summary>
        /// Gets or sets the comment block node.
        /// </summary>
        public CommentBlockNode CommentBlockNode
        {
            get
            {
                return commentBlockNode;
            }
            set
            {
                if (value != commentBlockNode)
                {
                    commentBlockNode = value;
                }
            }
        }

        /// <summary>
        /// Initializes a new instance of a <see cref="CommentBlockScanner"/> class.
        /// </summary>
        public CommentBlockScanner() : this(null, null, null)
        {
            //
        }

        /// <summary>
        /// Initializes a new instance of a <see cref="CommentBlockScanner"/> class.
        /// </summary>
        /// <param name="tokenizer">The <see cref="TokenizerBase"/> which is used to tokenize the source code.</param>
        /// <param name="scannerResult">The <see cref="OccurrenceCollection"/> which will contain the scanner result.</param>
        public CommentBlockScanner(TokenizerBase tokenizer, OccurrenceCollection scannerResult) : this(tokenizer, scannerResult, new CommentBlockNode())
        {
            //
        }

        /// <summary>
        /// Initializes a new instance of a <see cref="CommentBlockScanner"/> class.
        /// </summary>
        /// <param name="tokenizer">The <see cref="TokenizerBase"/> which is used to tokenize the source code.</param>
        /// <param name="scannerResult">The <see cref="OccurrenceCollection"/> which will contain the scanner result.</param>
        /// <param name="commentBlockNode">A <see cref="Engine.Node.Implementation.CommentBlock.CommentBlockNode"/> object.</param>
        public CommentBlockScanner(TokenizerBase tokenizer, OccurrenceCollection scannerResult, CommentBlockNode commentBlockNode) : base(tokenizer, scannerResult)
        {
            this.commentBlockNode = commentBlockNode;
            SetID("CommentBlockScanner");
        }

        /// <summary>
        /// Initializes a new instance of a <see cref="IScanner"/> implementation class.
        /// </summary>
        /// <param name="tokenizer">The <see cref="TokenizerBase"/> which is used to tokenize the source code.</param>
        /// <param name="scannerResult">The <see cref="OccurrenceCollection"/> which will contain the scanner result.</param>
        /// <returns>A new instance of a <see cref="IScanner"/> implementation class.</returns>
        public override IScanner Create(TokenizerBase tokenizer, OccurrenceCollection scannerResult)
        {
            return new CommentBlockScanner(tokenizer, scannerResult);
        }

        /// <summary>
        /// Loads the state of the scanner.
        /// </summary>
        /// <param name="state">An <see cref="System.Object"/> that contains the state of the scanner.</param>
        public override void LoadState(object state)
        {
            XmlElement element = (XmlElement)state;
			
            // Load settings.
            FontConverter converter = new FontConverter();
            commentBlockNode.BackColor = ColorTranslator.FromHtml(element.SelectSingleNode("settings/setting[@name='BackColor']").InnerText);
            commentBlockNode.ForeColor = ColorTranslator.FromHtml(element.SelectSingleNode("settings/setting[@name='ForeColor']").InnerText);
            commentBlockNode.Font = (Font)converter.ConvertFromString(element.SelectSingleNode("settings/setting[@name='Font']").InnerText);
            commentBlockNode.NavigateUrl = element.SelectSingleNode("settings/setting[@name='NavigateUrl']").InnerText;

            // Load entities.
            foreach (XmlElement entityElement in element["entities"].ChildNodes)
            {
                commentBlockNode.Entities.Add(new Entity(entityElement.Attributes["start"].Value, entityElement.Attributes["end"].Value));
            }
        }

        /// <summary>
        /// Saves the current state of the scanner.
        /// </summary>
        /// <param name="container">The container which will contain the state.</param>
        /// <returns>An <see cref="System.Object"/> that contains the state of the scanner.</returns>
        public override object SaveState(object container)
        {
            XmlDocument document = (XmlDocument)container;
            XmlElement element = (XmlElement)base.SaveState(container);
			
            // Save settings.
            XmlElement settingRootElement = document.CreateElement("settings");
            element.AppendChild(settingRootElement);

            FontConverter converter = new FontConverter();
            settingRootElement.AppendChild(CreateSetting(document, "BackColor", ColorTranslator.ToHtml(commentBlockNode.BackColor)));
            settingRootElement.AppendChild(CreateSetting(document, "ForeColor", ColorTranslator.ToHtml(commentBlockNode.ForeColor)));
            settingRootElement.AppendChild(CreateSetting(document, "Font", converter.ConvertToString(commentBlockNode.Font)));
            settingRootElement.AppendChild(CreateSetting(document, "NavigateUrl", commentBlockNode.NavigateUrl));

            // Save entities.
            XmlElement entityRootElement = document.CreateElement("entities");
            element.AppendChild(entityRootElement);
            XmlElement entityElement;
            foreach (Entity entity in commentBlockNode.Entities)
            {
                entityElement = document.CreateElement("entity");
                entityElement.SetAttribute("start", entity.Start);
                entityElement.SetAttribute("end", entity.End);
                entityRootElement.AppendChild(entityElement);
            }

            return element;
        }

        /// <summary>
        /// Creates a steting.
        /// </summary>
        /// <param name="document">The document which will contain the setting.</param>
        /// <param name="name">The name of the setting.</param>
        /// <param name="value">The value of the setting.</param>
        /// <returns>The <see cref="System.Xml.XmlElement"/> which represents the setting.</returns>
        private static XmlElement CreateSetting(XmlDocument document, string name, string value)
        {
            XmlElement element = document.CreateElement("setting");
            element.SetAttribute("name", name);
            element.InnerText = value;
            return element;
        }

        /// <summary>
        /// Scans a token.
        /// </summary>
        /// <remarks>
        /// An <see cref="IScanner"/> implementation will generally have a reference to a 
        /// <see cref="NodeCollection"/> which will be used to store results of a scan.
        /// </remarks>
        /// <param name="token">A token from the source code.</param>
        public override void Scan(string token)
        {
            if (!Enabled)
            {
                if (Child != null)
                {
                    Child.Scan(token);
                }
            }
            else
            {
                bool isMatch = false;

                for (int i = 0; i < commentBlockNode.Entities.Count; i++)
                {
                    if ((Tokenizer.Position + commentBlockNode.Entities[i].Start.Length) <= Tokenizer.Source.Length)
                    {
                        if (Tokenizer.GetNextTokens(commentBlockNode.Entities[i].Start.Length) == commentBlockNode.Entities[i].Start)
                        {
                            isMatch = true;

                            int endOfLineIndex = Tokenizer.Source.IndexOf(commentBlockNode.Entities[i].End, Tokenizer.Position);
                            if (endOfLineIndex == -1)
                                endOfLineIndex = Tokenizer.Source.Length;
                            else
                                endOfLineIndex += commentBlockNode.Entities[i].End.Length;

                            ScannerResult.Add(new Occurrence(Tokenizer.Position, endOfLineIndex - Tokenizer.Position, commentBlockNode));
                            Tokenizer.MoveTo(endOfLineIndex - 1);
                            break;
                        }
                    }
                }

                if (!isMatch && Child != null)
                {
                    Child.Scan(token);
                }
            }
        }
    }
}